Completed
Push — master ( 0485dc...b2f8f6 )
by Yannick
33:38
created

g   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
dl 0
loc 8
rs 9.4285
c 0
b 0
f 0
nop 3
1
// UMD initialization to work with CommonJS, AMD and basic browser script include
2
(function (factory) {
3
	var L;
4
	if (typeof define === 'function' && define.amd) {
0 ignored issues
show
Bug introduced by
The variable define seems to be never declared. If this is a global, consider adding a /** global: define */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
5
		// AMD
6
		define(['leaflet'], factory);
7
	} else if (typeof module === 'object' && typeof module.exports === "object") {
8
		// Node/CommonJS
9
		L = require('leaflet');
10
		module.exports = factory(L);
11
	} else {
12
		// Browser globals
13
		if (typeof window.L === 'undefined')
14
			throw 'Leaflet must be loaded first';
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
15
		factory(window.L);
16
	}
17
}(function (L) {
18
19
L.Playback = L.Playback || {};
20
21
L.Playback.Util = L.Class.extend({
22
  statics: {
23
24
    DateStr: function(time) {
25
      return new Date(time).toDateString();
26
    },
27
28
    TimeStr: function(time) {
29
      var d = new Date(time);
30
      var h = d.getHours();
31
      var m = d.getMinutes();
32
      var s = d.getSeconds();
33
      var tms = time / 1000;
34
      var dec = (tms - Math.floor(tms)).toFixed(2).slice(1);
35
      var mer = 'AM';
36
      if (h > 11) {
37
        h %= 12;
38
        mer = 'PM';
39
      } 
40
      if (h === 0) h = 12;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
41
      if (m < 10) m = '0' + m;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
42
      if (s < 10) s = '0' + s;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
43
      return h + ':' + m + ':' + s + dec + ' ' + mer;
44
    },
45
46
    ParseGPX: function(gpx) {
47
      var geojson = {
48
        type: 'Feature',
49
        geometry: {
50
          type: 'MultiPoint',
51
          coordinates: []
52
        },
53
        properties: {
54
          time: [],
55
          speed: [],
56
          altitude: []
57
        },
58
        bbox: []
59
      };
60
      var xml = $.parseXML(gpx);
61
      var pts = $(xml).find('trkpt');
62
      for (var i=0, len=pts.length; i<len; i++) {
63
        var p = pts[i];
64
        var lat = parseFloat(p.getAttribute('lat'));
65
        var lng = parseFloat(p.getAttribute('lon'));
66
        var timeStr = $(p).find('time').text();
67
        var eleStr = $(p).find('ele').text();
68
        var t = new Date(timeStr).getTime();
69
        var ele = parseFloat(eleStr);
70
71
        var coords = geojson.geometry.coordinates;
72
        var props = geojson.properties;
73
        var time = props.time;
74
        var altitude = geojson.properties.altitude;
75
76
        coords.push([lng,lat]);
77
        time.push(t);
78
        altitude.push(ele);
79
      }
80
      return geojson;
81
    }
82
  }
83
84
});
85
86
L.Playback = L.Playback || {};
87
88
L.Playback.MoveableMarker = L.Marker.extend({    
89
    initialize: function (startLatLng, options, feature) {
90
        var marker_options = options.marker || {};
91
92
        if (jQuery.isFunction(marker_options)){
93
            marker_options = marker_options(feature);
94
        }
95
        
96
        L.Marker.prototype.initialize.call(this, startLatLng, marker_options);
97
        
98
        this.popupContent = '';
99
        this.feature = feature;
100
		
101
        if (marker_options.getPopup){
102
            this.popupContent = marker_options.getPopup(feature);
103
        }
104
        
105
        if(options.popups)
106
        {
107
            this.bindPopup(this.getPopupContent() + startLatLng.toString());
108
        }
109
        	
110
        if(options.labels)
111
        {
112
            if(this.bindLabel)
113
            {
114
                this.bindLabel(this.getPopupContent());
115
            }
116
            else
117
            {
118
                console.log("Label binding requires leaflet-label (https://github.com/Leaflet/Leaflet.label)");
0 ignored issues
show
Debugging Code introduced by
console.log looks like debug code. Are you sure you do not want to remove it?
Loading history...
119
            }
120
        }
121
    },
122
    
123
    getPopupContent: function() {
124
        if (this.popupContent !== ''){
125
            return '<b>' + this.popupContent + '</b><br/>';
126
        }
127
        
128
        return '';
129
    },
130
131
    move: function (latLng, transitionTime) {
132
        // Only if CSS3 transitions are supported
133
        if (L.DomUtil.TRANSITION) {
134
            if (this._icon) { 
135
                this._icon.style[L.DomUtil.TRANSITION] = 'all ' + transitionTime + 'ms linear'; 
136
                if (this._popup && this._popup._wrapper)
137
                    this._popup._wrapper.style[L.DomUtil.TRANSITION] = 'all ' + transitionTime + 'ms linear'; 
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
138
            }
139
            if (this._shadow) { 
140
                this._shadow.style[L.DomUtil.TRANSITION] = 'all ' + transitionTime + 'ms linear'; 
141
            }
142
        }
143
        this.setLatLng(latLng);
144
        if (this._popup) {
145
            this._popup.setContent(this.getPopupContent() + this._latlng.toString());
146
        }
147
    }
148
});
149
150
L.Playback = L.Playback || {};
151
152
153
        
154
L.Playback.Track = L.Class.extend({
155
156
        initialize : function (geoJSON, options) {
157
            options = options || {};
158
            var tickLen = options.tickLen || 250;
159
            this._staleTime = options.staleTime || 60*60*1000;
160
            this._fadeMarkersWhenStale = options.fadeMarkersWhenStale || false;
161
            
162
            this._geoJSON = geoJSON;
163
            this._tickLen = tickLen;
164
            this._ticks = [];
165
            this._marker = null;
166
			this._orientations = [];
167
			
168
            var sampleTimes = geoJSON.properties.time;
169
			
170
            this._orientIcon = options.orientIcons;
171
            var previousOrientation;
172
			
173
            var samples = geoJSON.geometry.coordinates;
174
            var currSample = samples[0];
175
            var nextSample = samples[1];
176
			
177
            var currSampleTime = sampleTimes[0];
178
            var t = currSampleTime;  // t is used to iterate through tick times
179
            var nextSampleTime = sampleTimes[1];
180
            var tmod = t % tickLen; // ms past a tick time
181
            var rem,
182
            ratio;
183
184
            // handle edge case of only one t sample
185
            if (sampleTimes.length === 1) {
186
                if (tmod !== 0)
187
                    t += tickLen - tmod;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
188
                this._ticks[t] = samples[0];
189
				this._orientations[t] = 0;
190
                this._startTime = t;
191
                this._endTime = t;
192
                return;
193
            }
194
195
            // interpolate first tick if t not a tick time
196
            if (tmod !== 0) {
197
                rem = tickLen - tmod;
198
                ratio = rem / (nextSampleTime - currSampleTime);
199
                t += rem;
200
                this._ticks[t] = this._interpolatePoint(currSample, nextSample, ratio);
201
				this._orientations[t] = this._directionOfPoint(currSample,nextSample);
202
                previousOrientation = this._orientations[t];
203
            } else {
204
                this._ticks[t] = currSample;
205
				this._orientations[t] = this._directionOfPoint(currSample,nextSample);
206
                previousOrientation = this._orientations[t];
207
            }
208
209
            this._startTime = t;
210
            t += tickLen;
211
            while (t < nextSampleTime) {
212
                ratio = (t - currSampleTime) / (nextSampleTime - currSampleTime);
213
                this._ticks[t] = this._interpolatePoint(currSample, nextSample, ratio);
214
				this._orientations[t] = this._directionOfPoint(currSample,nextSample);
215
                previousOrientation = this._orientations[t];
216
                t += tickLen;
217
            }
218
219
            // iterating through the rest of the samples
220
            for (var i = 1, len = samples.length; i < len; i++) {
221
                currSample = samples[i];
222
                nextSample = samples[i + 1];
223
                t = currSampleTime = sampleTimes[i];
224
                nextSampleTime = sampleTimes[i + 1];
225
226
                tmod = t % tickLen;
227
                if (tmod !== 0 && nextSampleTime) {
228
                    rem = tickLen - tmod;
229
                    ratio = rem / (nextSampleTime - currSampleTime);
230
                    t += rem;
231
                    this._ticks[t] = this._interpolatePoint(currSample, nextSample, ratio);
232
					if(nextSample){
233
                        this._orientations[t] = this._directionOfPoint(currSample,nextSample);
234
                        previousOrientation = this._orientations[t];
235
                    } else {
236
                        this._orientations[t] = previousOrientation;    
237
                    }
238
                } else {
239
                    this._ticks[t] = currSample;
240
                    if(nextSample){
241
                        this._orientations[t] = this._directionOfPoint(currSample,nextSample);
242
                        previousOrientation = this._orientations[t];
243
                    } else {
244
                        this._orientations[t] = previousOrientation;    
245
                    }
246
                }
247
248
                t += tickLen;
249
                while (t < nextSampleTime) {
250
                    ratio = (t - currSampleTime) / (nextSampleTime - currSampleTime);
251
                    
252
                    if (nextSampleTime - currSampleTime > options.maxInterpolationTime){
253
                        this._ticks[t] = currSample;
254
                        
255
						if(nextSample){
256
                            this._orientations[t] = this._directionOfPoint(currSample,nextSample);
257
                            previousOrientation = this._orientations[t];
258
                        } else {
259
                            this._orientations[t] = previousOrientation;    
260
                        }
261
                    }
262
                    else {
263
                        this._ticks[t] = this._interpolatePoint(currSample, nextSample, ratio);
264
						if(nextSample) {
265
                            this._orientations[t] = this._directionOfPoint(currSample,nextSample);
266
                            previousOrientation = this._orientations[t];
267
                        } else {
268
                            this._orientations[t] = previousOrientation;    
269
                        }
270
                    }
271
                    
272
                    t += tickLen;
273
                }
274
            }
275
276
            // the last t in the while would be past bounds
277
            this._endTime = t - tickLen;
278
            this._lastTick = this._ticks[this._endTime];
279
280
        },
281
282
        _interpolatePoint : function (start, end, ratio) {
283
            try {
284
                var delta = [end[0] - start[0], end[1] - start[1]];
285
                var offset = [delta[0] * ratio, delta[1] * ratio];
286
                return [start[0] + offset[0], start[1] + offset[1]];
287
            } catch (e) {
288
                console.log('err: cant interpolate a point');
0 ignored issues
show
Debugging Code introduced by
console.log looks like debug code. Are you sure you do not want to remove it?
Loading history...
289
                console.log(['start', start]);
290
                console.log(['end', end]);
291
                console.log(['ratio', ratio]);
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
292
            }
293
        },
294
        
295
        _directionOfPoint:function(start,end){
296
            return this._getBearing(start[1],start[0],end[1],end[0]);
297
        },
298
        
299
        _getBearing:function(startLat,startLong,endLat,endLong){
300
              startLat = this._radians(startLat);
301
              startLong = this._radians(startLong);
302
              endLat = this._radians(endLat);
303
              endLong = this._radians(endLong);
304
305
              var dLong = endLong - startLong;
306
307
              var dPhi = Math.log(Math.tan(endLat/2.0+Math.PI/4.0)/Math.tan(startLat/2.0+Math.PI/4.0));
308
              if (Math.abs(dLong) > Math.PI){
309
                if (dLong > 0.0)
310
                   dLong = -(2.0 * Math.PI - dLong);
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
311
                else
312
                   dLong = (2.0 * Math.PI + dLong);
313
              }
314
315
              return (this._degrees(Math.atan2(dLong, dPhi)) + 360.0) % 360.0;
316
        },
317
        
318
        _radians:function(n) {
319
          return n * (Math.PI / 180);
320
        },
321
        _degrees:function(n) {
322
          return n * (180 / Math.PI);
323
        },
324
325
        getFirstTick : function () {
326
            return this._ticks[this._startTime];
327
        },
328
329
        getLastTick : function () {
330
            return this._ticks[this._endTime];
331
        },
332
333
        getStartTime : function () {
334
            return this._startTime;
335
        },
336
337
        getEndTime : function () {
338
            return this._endTime;
339
        },
340
341
        getTickMultiPoint : function () {
342
            var t = this.getStartTime();
343
            var endT = this.getEndTime();
344
            var coordinates = [];
345
            var time = [];
346
            while (t <= endT) {
347
                time.push(t);
348
                coordinates.push(this.tick(t));
349
                t += this._tickLen;
350
            }
351
352
            return {
353
                type : 'Feature',
354
                geometry : {
355
                    type : 'MultiPoint',
356
                    coordinates : coordinates
357
                },
358
                properties : {
359
                    time : time
360
                }
361
            };
362
        },
363
		
364
        trackPresentAtTick : function(timestamp)
365
        {
366
            return (timestamp >= this._startTime);
367
        },
368
        
369
        trackStaleAtTick : function(timestamp)
370
        {
371
            return ((this._endTime + this._staleTime) <= timestamp);
372
        },
373
374
        tick : function (timestamp) {
375
            if (timestamp > this._endTime)
376
                timestamp = this._endTime;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
377
            if (timestamp < this._startTime)
378
                timestamp = this._startTime;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
379
            return this._ticks[timestamp];
380
        },
381
		
382
        courseAtTime: function(timestamp)
383
        {
384
            //return 90;
385
            if (timestamp > this._endTime)
386
               timestamp = this._endTime;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
387
            if (timestamp < this._startTime)
388
                timestamp = this._startTime;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
389
            return this._orientations[timestamp];
390
        },
391
        
392
        setMarker : function(timestamp, options){
393
            var lngLat = null;
0 ignored issues
show
Unused Code introduced by
The assignment to lngLat seems to be never used. If you intend to free memory here, this is not necessary since the variable leaves the scope anyway.
Loading history...
394
            
395
            // if time stamp is not set, then get first tick
396
            if (timestamp) {
397
                lngLat = this.tick(timestamp);
398
            }
399
            else {
400
                lngLat = this.getFirstTick();
401
            }        
402
        
403
            if (lngLat) {
404
                var latLng = new L.LatLng(lngLat[1], lngLat[0]);
405
                this._marker = new L.Playback.MoveableMarker(latLng, options, this._geoJSON);     
406
				if(options.mouseOverCallback) {
407
                    this._marker.on('mouseover',options.mouseOverCallback);
408
                }
409
				if(options.clickCallback) {
410
                    this._marker.on('click',options.clickCallback);
411
                }
412
				
413
				//hide the marker if its not present yet and fadeMarkersWhenStale is true
414
				if(this._fadeMarkersWhenStale && !this.trackPresentAtTick(timestamp))
415
				{
416
					this._marker.setOpacity(0);
417
				}
418
            }
419
            
420
            return this._marker;
421
        },
422
        
423
        moveMarker : function(latLng, transitionTime,timestamp) {
424
            if (this._marker) {
425
                if(this._fadeMarkersWhenStale) {
426
                    //show the marker if its now present
427
                    if(this.trackPresentAtTick(timestamp)) {
428
                        this._marker.setOpacity(1);
429
                    } else {
430
                        this._marker.setOpacity(0);
431
                    }
432
                    
433
                    if(this.trackStaleAtTick(timestamp)) {
434
                        this._marker.setOpacity(0.25);
435
                    }
436
                }
437
				
438
                if(this._orientIcon){
439
                    this._marker.setIconAngle(this.courseAtTime(timestamp));
440
                }
441
				
442
                this._marker.move(latLng, transitionTime);
443
            }
444
        },
445
        
446
        getMarker : function() {
447
            return this._marker;
448
        }
449
450
    });
451
452
L.Playback = L.Playback || {};
453
454
L.Playback.TrackController = L.Class.extend({
455
456
    initialize : function (map, tracks, options) {
457
        this.options = options || {};
458
    
459
        this._map = map;
460
461
        this._tracks = [];
462
463
        // initialize tick points
464
        this.setTracks(tracks);
465
    },
466
    
467
    clearTracks: function(){
468
        while (this._tracks.length > 0) {
469
            var track = this._tracks.pop();
470
            var marker = track.getMarker();
471
            
472
            if (marker){
473
                this._map.removeLayer(marker);
474
            }
475
        }            
476
    },
477
478
    setTracks : function (tracks) {
479
        // reset current tracks
480
        this.clearTracks();
481
        
482
        this.addTracks(tracks);
483
    },
484
    
485
    addTracks : function (tracks) {
486
        // return if nothing is set
487
        if (!tracks) {
488
            return;
489
        }
490
        
491
        if (tracks instanceof Array) {            
492
            for (var i = 0, len = tracks.length; i < len; i++) {
493
                this.addTrack(tracks[i]);
494
            }
495
        } else {
496
            this.addTrack(tracks);
497
        }            
498
    },
499
    
500
    // add single track
501
    addTrack : function (track, timestamp) {
502
        // return if nothing is set
503
        if (!track) {
504
            return;
505
        }
506
507
        var marker = track.setMarker(timestamp, this.options);
508
509
        if (marker) {
510
            marker.addTo(this._map);
511
            
512
            this._tracks.push(track);
513
        }            
514
    },
515
516
    tock : function (timestamp, transitionTime) {
517
        for (var i = 0, len = this._tracks.length; i < len; i++) {
518
            var lngLat = this._tracks[i].tick(timestamp);
519
            var latLng = new L.LatLng(lngLat[1], lngLat[0]);
520
            this._tracks[i].moveMarker(latLng, transitionTime,timestamp);
521
        }
522
    },
523
524
    getStartTime : function () {
525
        var earliestTime = 0;
526
527
        if (this._tracks.length > 0) {
528
            earliestTime = this._tracks[0].getStartTime();
529
            for (var i = 1, len = this._tracks.length; i < len; i++) {
530
                var t = this._tracks[i].getStartTime();
531
                if (t < earliestTime) {
532
                    earliestTime = t;
533
                }
534
            }
535
        }
536
        
537
        return earliestTime;
538
    },
539
540
    getEndTime : function () {
541
        var latestTime = 0;
542
    
543
        if (this._tracks.length > 0){
544
            latestTime = this._tracks[0].getEndTime();
545
            for (var i = 1, len = this._tracks.length; i < len; i++) {
546
                var t = this._tracks[i].getEndTime();
547
                if (t > latestTime) {
548
                    latestTime = t;
549
                }
550
            }
551
        }
552
    
553
        return latestTime;
554
    },
555
556
    getTracks : function () {
557
        return this._tracks;
558
    }
559
});
560
L.Playback = L.Playback || {};
561
562
L.Playback.Clock = L.Class.extend({
563
564
  initialize: function (trackController, callback, options) {
565
    this._trackController = trackController;
566
    this._callbacksArry = [];
567
    if (callback) this.addCallback(callback);
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
568
    L.setOptions(this, options);
569
    this._speed = this.options.speed;
570
    this._tickLen = this.options.tickLen;
571
    this._cursor = trackController.getStartTime();
572
    this._transitionTime = this._tickLen / this._speed;
573
  },
574
575
  _tick: function (self) {
576
    if (self._cursor > self._trackController.getEndTime()) {
577
      clearInterval(self._intervalID);
578
      return;
579
    }
580
    self._trackController.tock(self._cursor, self._transitionTime);
581
    self._callbacks(self._cursor);
582
    self._cursor += self._tickLen;
583
  },
584
585
  _callbacks: function(cursor) {
586
    var arry = this._callbacksArry;
587
    for (var i=0, len=arry.length; i<len; i++) {
588
      arry[i](cursor);
589
    }
590
  },
591
592
  addCallback: function(fn) {
593
    this._callbacksArry.push(fn);
594
  },
595
596
  start: function () {
597
    if (this._intervalID) return;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
598
    this._intervalID = window.setInterval(
599
      this._tick, 
600
      this._transitionTime, 
601
      this);
602
  },
603
604
  stop: function () {
605
    if (!this._intervalID) return;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
606
    clearInterval(this._intervalID);
607
    this._intervalID = null;
608
  },
609
610
  getSpeed: function() {
611
    return this._speed;
612
  },
613
614
  isPlaying: function() {
615
    return this._intervalID ? true : false;
616
  },
617
618
  setSpeed: function (speed) {
619
    this._speed = speed;
620
    this._transitionTime = this._tickLen / speed;
621
    if (this._intervalID) {
622
      this.stop();
623
      this.start();
624
    }
625
  },
626
627
  setCursor: function (ms) {
628
    var time = parseInt(ms);
629
    if (!time) return;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
630
    var mod = time % this._tickLen;
631
    if (mod !== 0) {
632
      time += this._tickLen - mod;
633
    }
634
    this._cursor = time;
635
    this._trackController.tock(this._cursor, 0);
636
    this._callbacks(this._cursor);
637
  },
638
639
  getTime: function() {
640
    return this._cursor;
641
  },
642
643
  getStartTime: function() {
644
    return this._trackController.getStartTime();
645
  },
646
647
  getEndTime: function() {
648
    return this._trackController.getEndTime();
649
  },
650
651
  getTickLen: function() {
652
    return this._tickLen;
653
  }
654
655
});
656
657
// Simply shows all of the track points as circles.
658
// TODO: Associate circle color with the marker color.
659
660
L.Playback = L.Playback || {};
661
662
L.Playback.TracksLayer = L.Class.extend({
663
    initialize : function (map, options) {
664
        var layer_options = options.layer || {};
665
        
666
        if (jQuery.isFunction(layer_options)){
667
            layer_options = layer_options(feature);
0 ignored issues
show
Bug introduced by
The variable feature seems to be never declared. If this is a global, consider adding a /** global: feature */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
668
        }
669
        
670
        if (!layer_options.pointToLayer) {
671
            layer_options.pointToLayer = function (featureData, latlng) {
672
                return new L.CircleMarker(latlng, { radius : 5 });
673
            };
674
        }
675
    
676
        this.layer = new L.GeoJSON(null, layer_options);
677
678
        var overlayControl = {
679
            'GPS Tracks' : this.layer
680
        };
681
682
        L.control.layers(null, overlayControl, {
683
            collapsed : false
684
        }).addTo(map);
685
    },
686
687
    // clear all geoJSON layers
688
    clearLayer : function(){
689
        for (var i in this.layer._layers) {
0 ignored issues
show
Complexity introduced by
A for in loop automatically includes the property of any prototype object, consider checking the key using hasOwnProperty.

When iterating over the keys of an object, this includes not only the keys of the object, but also keys contained in the prototype of that object. It is generally a best practice to check for these keys specifically:

var someObject;
for (var key in someObject) {
    if ( ! someObject.hasOwnProperty(key)) {
        continue; // Skip keys from the prototype.
    }

    doSomethingWith(key);
}
Loading history...
690
            this.layer.removeLayer(this.layer._layers[i]);            
691
        }
692
    },
693
694
    // add new geoJSON layer
695
    addLayer : function(geoJSON) {
696
        this.layer.addData(geoJSON);
697
    }
698
});
699
L.Playback = L.Playback || {};
700
701
L.Playback.DateControl = L.Control.extend({
702
    options : {
703
        position : 'bottomleft',
704
        dateFormatFn: L.Playback.Util.DateStr,
705
        timeFormatFn: L.Playback.Util.TimeStr
706
    },
707
708
    initialize : function (playback, options) {
709
        L.setOptions(this, options);
710
        this.playback = playback;
711
    },
712
713
    onAdd : function (map) {
0 ignored issues
show
Unused Code introduced by
The parameter map is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
714
        this._container = L.DomUtil.create('div', 'leaflet-control-layers leaflet-control-layers-expanded');
715
716
        var self = this;
717
        var playback = this.playback;
718
        var time = playback.getTime();
719
720
        var datetime = L.DomUtil.create('div', 'datetimeControl', this._container);
721
722
        // date time
723
        this._date = L.DomUtil.create('p', '', datetime);
724
        this._time = L.DomUtil.create('p', '', datetime);
725
726
        this._date.innerHTML = this.options.dateFormatFn(time);
727
        this._time.innerHTML = this.options.timeFormatFn(time);
728
729
        // setup callback
730
        playback.addCallback(function (ms) {
731
            self._date.innerHTML = self.options.dateFormatFn(ms);
732
            self._time.innerHTML = self.options.timeFormatFn(ms);
733
        });
734
735
        return this._container;
736
    }
737
});
738
    
739
L.Playback.PlayControl = L.Control.extend({
740
    options : {
741
        position : 'bottomright'
742
    },
743
744
    initialize : function (playback) {
745
        this.playback = playback;
746
    },
747
748
    onAdd : function (map) {
0 ignored issues
show
Unused Code introduced by
The parameter map is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
749
        this._container = L.DomUtil.create('div', 'leaflet-control-layers leaflet-control-layers-expanded');
750
751
        var self = this;
752
        var playback = this.playback;
753
        playback.setSpeed(100);
754
755
        var playControl = L.DomUtil.create('div', 'playControl', this._container);
756
757
758
        this._button = L.DomUtil.create('button', '', playControl);
759
        this._button.innerHTML = 'Play';
760
761
762
        var stop = L.DomEvent.stopPropagation;
763
764
        L.DomEvent
765
        .on(this._button, 'click', stop)
766
        .on(this._button, 'mousedown', stop)
767
        .on(this._button, 'dblclick', stop)
768
        .on(this._button, 'click', L.DomEvent.preventDefault)
769
        .on(this._button, 'click', play, this);
770
        
771
        function play(){
772
            if (playback.isPlaying()) {
773
                playback.stop();
774
                self._button.innerHTML = 'Play';
775
            }
776
            else {
777
                playback.start();
778
                self._button.innerHTML = 'Stop';
779
            }                
780
        }
781
782
        return this._container;
783
    }
784
});    
785
    
786
L.Playback.SliderControl = L.Control.extend({
787
    options : {
788
        position : 'bottomleft'
789
    },
790
791
    initialize : function (playback) {
792
        this.playback = playback;
793
    },
794
795
    onAdd : function (map) {
796
        this._container = L.DomUtil.create('div', 'leaflet-control-layers leaflet-control-layers-expanded');
797
798
        var self = this;
799
        var playback = this.playback;
800
801
        // slider
802
        this._slider = L.DomUtil.create('input', 'slider', this._container);
803
        this._slider.type = 'range';
804
        this._slider.min = playback.getStartTime();
805
        this._slider.max = playback.getEndTime();
806
        this._slider.value = playback.getTime();
807
808
        var stop = L.DomEvent.stopPropagation;
809
810
        L.DomEvent
811
        .on(this._slider, 'click', stop)
812
        .on(this._slider, 'mousedown', stop)
813
        .on(this._slider, 'dblclick', stop)
814
        .on(this._slider, 'click', L.DomEvent.preventDefault)
815
        //.on(this._slider, 'mousemove', L.DomEvent.preventDefault)
816
        .on(this._slider, 'change', onSliderChange, this)
817
        .on(this._slider, 'mousemove', onSliderChange, this);           
818
819
820
        function onSliderChange(e) {
821
            var val = Number(e.target.value);
822
            playback.setCursor(val);
823
        }
824
825
        playback.addCallback(function (ms) {
826
            self._slider.value = ms;
827
        });
828
        
829
        
830
        map.on('playback:add_tracks', function() {
831
            self._slider.min = playback.getStartTime();
832
            self._slider.max = playback.getEndTime();
833
            self._slider.value = playback.getTime();
834
        });
835
836
        return this._container;
837
    }
838
});      
839
840
L.Playback = L.Playback.Clock.extend({
841
        statics : {
842
            MoveableMarker : L.Playback.MoveableMarker,
843
            Track : L.Playback.Track,
844
            TrackController : L.Playback.TrackController,
845
            Clock : L.Playback.Clock,
846
            Util : L.Playback.Util,
847
            
848
            TracksLayer : L.Playback.TracksLayer,
849
            PlayControl : L.Playback.PlayControl,
850
            DateControl : L.Playback.DateControl,
851
            SliderControl : L.Playback.SliderControl
852
        },
853
854
        options : {
855
            tickLen: 250,
856
            speed: 1,
857
            maxInterpolationTime: 5*60*1000, // 5 minutes
858
859
            tracksLayer : true,
860
            
861
            playControl: false,
862
            dateControl: false,
863
            sliderControl: false,
864
            
865
            // options
866
            layer: {
867
                // pointToLayer(featureData, latlng)
868
            },
869
            
870
            marker : {
871
                // getPopup(feature)
872
            }
873
        },
874
875
        initialize : function (map, geoJSON, callback, options) {
876
            L.setOptions(this, options);
877
            
878
            this._map = map;
879
            this._trackController = new L.Playback.TrackController(map, null, this.options);
880
            L.Playback.Clock.prototype.initialize.call(this, this._trackController, callback, this.options);
881
            
882
            if (this.options.tracksLayer) {
883
                this._tracksLayer = new L.Playback.TracksLayer(map, options);
884
            }
885
886
            this.setData(geoJSON);            
887
            
888
889
            if (this.options.playControl) {
890
                this.playControl = new L.Playback.PlayControl(this);
891
                this.playControl.addTo(map);
892
            }
893
894
            if (this.options.sliderControl) {
895
                this.sliderControl = new L.Playback.SliderControl(this);
896
                this.sliderControl.addTo(map);
897
            }
898
899
            if (this.options.dateControl) {
900
                this.dateControl = new L.Playback.DateControl(this, options);
901
                this.dateControl.addTo(map);
902
            }
903
904
        },
905
        
906
        clearData : function(){
907
            this._trackController.clearTracks();
908
            
909
            if (this._tracksLayer) {
910
                this._tracksLayer.clearLayer();
911
            }
912
        },
913
        
914
        setData : function (geoJSON) {
915
            this.clearData();
916
        
917
            this.addData(geoJSON, this.getTime());
918
            
919
            this.setCursor(this.getStartTime());
920
        },
921
922
        // bad implementation
923
        addData : function (geoJSON, ms) {
924
            // return if data not set
925
            if (!geoJSON) {
926
                return;
927
            }
928
        
929
            if (geoJSON instanceof Array) {
930
                for (var i = 0, len = geoJSON.length; i < len; i++) {
931
                    this._trackController.addTrack(new L.Playback.Track(geoJSON[i], this.options), ms);
932
                }
933
            } else {
934
                this._trackController.addTrack(new L.Playback.Track(geoJSON, this.options), ms);
935
            }
936
937
            this._map.fire('playback:set:data');
938
            
939
            if (this.options.tracksLayer) {
940
                this._tracksLayer.addLayer(geoJSON);
941
            }                  
942
        },
943
944
        destroy: function() {
945
            this.clearData();
946
            if (this.playControl) {
947
                this._map.removeControl(this.playControl);
948
            }
949
            if (this.sliderControl) {
950
                this._map.removeControl(this.sliderControl);
951
            }
952
            if (this.dateControl) {
953
                this._map.removeControl(this.dateControl);
954
            }
955
        }
956
    });
957
958
L.Map.addInitHook(function () {
959
    if (this.options.playback) {
960
        this.playback = new L.Playback(this);
961
    }
962
});
963
964
L.playback = function (map, geoJSON, callback, options) {
965
    return new L.Playback(map, geoJSON, callback, options);
966
};
967
return L.Playback;
968
969
}));
970